﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text.RegularExpressions;

namespace Sunlight.Dispatch.Tools {
    /// <summary>
    /// 编号生成器
    /// </summary>
    public class CodeGenerator {
        private readonly DbHelper db;

        // 流水号重置类型，分别为按自然日、自然月、自然年        
        private enum SerialResetType {
            None = 0,
            Daily = 1,
            Monthly = 2,
            Yearly = 3
        }
        // 仅匹配类似{DATE:yyyyMMdd}格式文本
        private readonly Regex regexp = new Regex(@"(?<=\{(?!\{))([^:}]+)(?=:*[^}]*\}(?!\}))");
        // 占位符值
        private SortedList<string, object> placeHolders = new SortedList<string, object>();

        private CodeGenerator(DbHelper dbHelper) {
            this.db = dbHelper;
        }

        /// <summary>
        /// 将文本占位符替换为索引占位符
        /// 
        /// 如{DATE:yyyyMMdd}替换为{0:yyyyMMdd}
        /// </summary>
        /// <param name="match">文本占位符</param>
        /// <returns>索引占位符</returns>
        private string ReplaceText(Match match) {
            var placeHolder = match.Groups[1].Value;
            if(placeHolders.ContainsKey(placeHolder))
                return placeHolders.IndexOfKey(placeHolder).ToString();
            throw new FormatException("未找到占位符" + placeHolder + "的替代值");
        }

        /// <summary>
        /// 根据编码规则生成新编号，该函数在单独的事务中完成。
        /// </summary>
        /// <param name="name">名称</param>
        /// <param name="parameters">附加参数</param>
        /// <returns>编号</returns>
        private string NewCode(string name, SortedList<string, object> parameters) {
            string result;

            using(var conn = db.CreateDbConnection()) {
                conn.Open();
                var id = "";
                var template = "";
                var resetType = 0;
                var commandTemplate = db.CreateDbCommand(string.Format("select Id, ResetType, Template from CodeTemplate where Name={0}Name and IsActived=1", db.ParamMark), conn, null);
                commandTemplate.Parameters.Add(db.CreateDbParameter("Name", name));
                var reader = commandTemplate.ExecuteReader();
                var hasValue = false;
                while(reader.Read()) {
                    if(hasValue)
                        throw new Exception("存在多个名称为" + name + "的编码规则");
                    else
                        hasValue = true;
                    id = reader[0].ToString();
                    resetType = int.Parse(reader[1].ToString());
                    template = reader[2].ToString();
                }
                reader.Close();
                if(!hasValue)
                    throw new Exception("未找到名称为" + name + "的有效编码规则");
                DateTime createDate;
                switch(resetType) {
                    case (int)SerialResetType.Daily:
                        createDate = DateTime.Today;
                        break;
                    case (int)SerialResetType.Monthly:
                        createDate = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
                        break;
                    case (int)SerialResetType.Yearly:
                        createDate = new DateTime(DateTime.Now.Year, 1, 1);
                        break;
                    default:
                        createDate = DateTime.MinValue;
                        break;
                }
                var corpCode = string.Empty;
                if(parameters.ContainsKey("CORPCODE") && parameters["CORPCODE"] != null)
                    corpCode = parameters["CORPCODE"].ToString();

                var command = db.CreateDbCommand("GetSerial", conn, null);
                command.CommandType = CommandType.StoredProcedure;
                command.Parameters.Add(db.CreateDbParameter("FTemplateId", id));
                command.Parameters.Add(db.CreateDbParameter("FCreateDate", createDate));
                command.Parameters.Add(db.CreateDbParameter("FCorpCode", corpCode));
                var param = db.CreateDbParameter("FSerial", 0);
#if !Oracle
                param.Direction = ParameterDirection.InputOutput;
#else
                param.Direction = ParameterDirection.ReturnValue;
#endif
                command.Parameters.Add(param);
                command.ExecuteNonQuery();
                var serial = param.Value;

                // 应用编码规则的模板
                placeHolders = parameters;
                placeHolders["DATE"] = DateTime.Now;
                placeHolders["SERIAL"] = serial;
                template = regexp.Replace(template, ReplaceText);
                result = string.Format(template, placeHolders.Values.ToArray());
            }
            return result;
        }

        /// <summary>
        /// 根据编码规则生成新编号，该函数在单独的事务中完成。
        /// 
        /// 编码规则模板可包含有多个格式为<c>{文本占位符:格式}</c>的元素。
        /// 其中占位符<c>DATE</c>表示当前服务端时间，<c>SERIAL</c>当前规则流水号。
        /// </summary>
        /// <example>
        /// <![CDATA[
        /// 例：某实体的编码规则为XS_{SALER}{DATE:yyyyMM}{SERIAL:000}，当前时间为2011-07-12 20:00:00，流水号为3。
        /// <code>
        /// // XS_HX20110712003
        /// CodeGenerator.Generate("SaleOrder", new SortedList<string, Object> { { "SALER", "HX" } });        
        /// </code>
        /// ]]>
        /// </example>
        /// <param name="dbHelper">用于获取编码规则数据库相关内容 </param>
        /// <param name="name">规则名称，用于从数据库中获取该名称的有效编码规则。</param>
        /// <param name="parameters">附加参数，用于替换模板中的对应文本占位符。</param>
        /// <returns>编号</returns>
        /// <exception cref="Exception">未找到该名称的有效编码规则。</exception>
        /// <exception cref="FormatException">未找到规则模板指定的占位符值。</exception>
        /// <exception cref="FormatException">规则模板格式有误，无法完成string.Format。</exception>
        private static string Generate(DbHelper dbHelper, string name, SortedList<string, Object> parameters) {
            return new CodeGenerator(dbHelper).NewCode(name, parameters);
        }

        /// <summary>
        /// 根据编码规则生成新编号，该函数在单独的事务中完成。
        ///         
        /// 编码规则模板可包含有多个格式为<c>{文本占位符:格式}</c>的元素。
        /// 其中占位符<c>DATE</c>表示当前服务端时间；
        /// <c>SERIAL</c>表示当前规则流水号；
        /// <c>CORPCODE</c>表示当前人员所属企业编号。
        /// </summary>
        /// <example>
        /// 例：某实体的编码规则为AS{DATE:yyyyMMdd}{SERIAL:000}，则返回值为AS20110712001。
        /// </example>
        /// <param name="dbHelper">用于获取编码规则数据库相关内容 </param>
        /// <param name="name">规则名称，用于从数据库中获取该名称的有效编码规则。</param>
        /// <returns>编号</returns>
        /// <exception cref="Exception">未找到该名称的有效编码规则。</exception>
        /// <exception cref="FormatException">未找到规则模板指定的占位符值。</exception>
        /// <exception cref="FormatException">规则模板格式有误，无法完成string.Format。</exception>
        public static string Generate(DbHelper dbHelper, string name) {
            return Generate(dbHelper, name, new SortedList<string, object>());
        }

        /// <summary>
        /// 根据编码规则生成新编号，该函数在单独的事务中完成。
        ///         
        /// 编码规则模板可包含有多个格式为<c>{文本占位符:格式}</c>的元素。
        /// 其中占位符<c>DATE</c>表示当前服务端时间；
        /// <c>SERIAL</c>表示当前规则流水号；
        /// <c>CORPCODE</c>表示当前人员所属企业编号。
        /// </summary>
        /// <example>
        /// <![CDATA[
        /// 例：某实体的编码规则为AS{DATE:yyyyMM}{SERIAL:000}_{CORPCODE}，当前时间为2011-07-12 20:00:00，流水号为3。
        /// <code>
        /// // AS20110712003_ES042
        /// CodeGenerator.Generate("AgencyService", "ES042");        
        /// </code>
        /// ]]>
        /// </example>///
        /// <param name="dbHelper">用于获取编码规则数据库相关内容 </param>
        /// <param name="name">规则名称，用于从数据库中获取该名称的有效编码规则。</param>
        /// <param name="corpCode">企业编号，当前人员所属企业编号，该参数影响流水号的取值</param>
        /// <returns>编号</returns>
        /// <exception cref="Exception">未找到该名称的有效编码规则。</exception>
        /// <exception cref="FormatException">未找到规则模板指定的占位符值。</exception>
        /// <exception cref="FormatException">规则模板格式有误，无法完成string.Format。</exception>
        public static string Generate(DbHelper dbHelper, string name, string corpCode) {
            return Generate(dbHelper, name, new SortedList<string, object> { { "CORPCODE", corpCode } });
        }
    }
}